package com.dtflys.easyel.runtime;

import com.dtflys.easyel.utils.MetaHelper;
import com.dtflys.easyel.utils.NumberHelper;
import com.dtflys.easyel.utils.TypeHelper;
import com.dtflys.easyel.runtime.wrapper.Wrapper;

/**
 * @author gongjun[jun.gong@thebeastshop.com]
 * @since v1.0.0
 */
public class MethodParameterTypes {

    private final Class[] types;

    private final boolean isVarArgs;

    private final int nonVarLength;

    private final Class varArgType;


    public MethodParameterTypes(Class[] paramTypes, boolean isVarArgs) {
        this.types = new Class[paramTypes.length];
        for (int i = 0; i < types.length; i++) {
            Class clazz = paramTypes[i];
            this.types[i] = clazz;
        }
        this.isVarArgs = isVarArgs;
        int len = this.types.length;
        this.nonVarLength = isVarArgs ? len - 1 : len;
        Class vType = isVarArgs ? types[len - 1] : null;
        if (vType != null) {
            this.varArgType = vType.getComponentType();
        } else {
            this.varArgType = null;
        }
    }


    public boolean isValidArguments(Object[] args) {
        if (nonVarLength > args.length) {
            return false;
        }
        if (nonVarLength < args.length && !isVarArgs) {
            return false;
        }
        for (int i = 0; i < nonVarLength; i++) {
            Class paramType = types[i];
            paramType = MetaHelper.autoboxType(paramType);
            Object arg = args[i];
            boolean valid = validateArgType(paramType, arg);
            if (!valid) return false;
        }

        if (isVarArgs) {
            int len = args.length;
            if (len <= nonVarLength || varArgType == null) {
                return false;
            }
            Class boxedVarArgType = MetaHelper.autoboxType(varArgType);
            for (int i = nonVarLength; i < len; i++) {
                Object arg = args[i];
                boolean valid = validateArgType(boxedVarArgType, arg);
                if (!valid) return false;
            }
        }
        return true;
    }

    private boolean validateArgType(Class paramType, Object arg) {
        Class argType = arg.getClass();
        if (arg instanceof Wrapper) {
            argType = ((Wrapper) arg).getOriginalClass();
        }
        if (Number.class.isAssignableFrom(paramType) &&
                Number.class.isAssignableFrom(argType)) {
            if (TypeHelper.isJavaInteger(paramType)
                    || TypeHelper.isJavaLong(paramType)
                    || TypeHelper.isJavaShort(paramType)
                    || TypeHelper.isBigInteger(paramType)) {
                if (TypeHelper.isJavaFloat(argType)
                        || TypeHelper.isJavaDouble(argType)
                        || TypeHelper.isBigDecimal(argType)) {
                    return NumberHelper.isRoundFigure((Number) arg);
                }
            }
            return true;
        }
        if (!paramType.isAssignableFrom(argType)) {
            return false;
        }
        return true;
    }

    public long calculateDistanceWithArguments(Object[] args) {
        int len = types.length;
        long distance = 0;
        for (int i = 0; i < len; i++) {
            Class paramType = types[i];
            Object arg = args[i];
            arg = MetaHelper.unwrap(arg);
            Class argType = arg.getClass();
            long d = calculateDistanceWithType(paramType, argType);
            distance += d;
        }
        return distance;
    }

    public long calculateDistanceWithType(Class paramType, Class argType) {
        MetaClass paramMetaClass = MetaClassFactory.getMetaClass(paramType);
        return paramMetaClass.getTypeDistance(argType);
    }

    public Class[] getTypes() {
        return types;
    }

    public int getTypeCount() {
        return types.length;
    }

    public boolean isVarArgs() {
        return isVarArgs;
    }

    public int getNonVarLength() {
        return nonVarLength;
    }

    public Class getVarArgType() {
        return varArgType;
    }

    public boolean equalsTypes(Class[] argTypes) {
        if (this.types.length != argTypes.length) {
            return false;
        }
        for (int i = 0; i < this.types.length; i++) {
            Class paramType = this.types[i];
            Class argType = argTypes[i];
            if (paramType != argType) {
                return false;
            }
        }
        return true;
    }



}
